/*
 * This file or a portion of this file is licensed under the terms of
 * the Globus Toolkit Public License, found in file ../GTPL, or at
 * http://www.globus.org/toolkit/download/license.html. This notice must
 * appear in redistributions of this file, with or without modification.
 *
 * Redistributions of this Software, with or without modification, must
 * reproduce the GTPL in: (1) the Software, or (2) the Documentation or
 * some other similar material which is provided with the Software (if
 * any).
 *
 * Copyright 1999-2004 University of Chicago and The University of
 * Southern California. All rights reserved.
 */
package org.griphyn.vdl.dbschema;

import java.sql.*;
import java.util.*;
import java.lang.reflect.*;
import java.sql.SQLException;
import java.io.IOException;
import org.griphyn.vdl.util.ChimeraProperties;
import org.griphyn.vdl.classes.*;
import org.griphyn.vdl.util.Logging;
import org.griphyn.vdl.dbdriver.*;

/**
 * This is a class that falls back not on a real database backend,
 * but rather on an existing Definitions data structure in main
 * memory. This schema is for internal use only.</p>
 *
 * @author Jens-S. Vöckler
 * @author Yong Zhao
 * @version $Revision: 50 $
 * @see org.griphyn.vdl.dbdriver
 * @see org.griphyn.vdl.classes.Definitions
 */
public class InMemorySchema extends DatabaseSchema implements VDC
{
  /**
   * Stores a reference to the in-memory data structure that hold
   * all definitions that we can access from within this instance.
   */
  protected Definitions m_memory; 

  /**
   * Default ctor does nothing.
   */
  protected InMemorySchema()
  {
    super();
    this.m_memory = null;
  }

  /**
   * Dirty hack: Returns a reference to the in-memory database for
   * preliminary routing into DAXes. This is to avoid the duplication
   * of DVs in memory, as memory becomes quickly a scarce resource.
   *
   * @return a reference to the in-memory database.
   */
  public Definitions backdoor() 
  {
    return this.m_memory;
  }

  /**
   * Fakes a connect to the database. This class never uses any
   * database, but instead applies all data to the provided 
   * reference to the in-memory structure. Subclasses may refine
   * this view to work with files or URLs. 
   *
   * @param memory is a reference to an existing in-memory Java
   * object holding all our necessary definitions.
   */
  public InMemorySchema( Definitions memory )
    throws ClassNotFoundException, 
	   NoSuchMethodException, InstantiationException, 
	   IllegalAccessException, InvocationTargetException,
	   SQLException, IOException
  {
    super(); // call minimalistic c'tor
    this.m_memory = memory;
    this.m_dbschemaprops = 
      ChimeraProperties.instance().getDatabaseSchemaProperties( PROPERTY_PREFIX );
  }

  /**
   * Pass-thru to driver. Always returns false, as the backend is
   * main memory.
   *
   * @return true, if it is feasible to cache results from the driver
   * false, if requerying the driver is sufficiently fast (e.g. driver
   * is in main memory, or driver does caching itself). 
   */
  public boolean cachingMakesSense()
  {
    return false;
  }

  //
  // lower level methods, working directly on specific definitions
  //

  /**
   * Loads a single Definition from the backend database into an Java object.
   * This method does not allow wildcarding!
   *
   * @param namespace   namespace, null will be converted into empty string
   * @param name        name, null will be converted into empty string
   * @param version     version, null will be converted into empty string
   * @param type     type of the definition (TR or DV), must not be -1.
   * @return the Definition as specified, or null if not found.
   *
   * @see org.griphyn.vdl.classes.Definition#TRANSFORMATION
   * @see org.griphyn.vdl.classes.Definition#DERIVATION
   * @see #saveDefinition( Definition, boolean )
   * @see #searchDefinition( String, String, String, int )
   */
  public Definition 
    loadDefinition( String namespace,
		    String name,
		    String version,
		    int type )
    throws SQLException
  {
    // walk main memory
    Definition result = null;
    for ( Iterator i=this.m_memory.iterateDefinition(); i.hasNext(); ) {
      Definition d = (Definition) i.next();
      if ( d.match( type, namespace, name, version ) ) {
        result = d;
        break;
      }
    }
    return result;
  }    


  /**
   * Saves a Definition, that is either a Transformation or Derivation, 
   * into the backend database. 
   *
   * @param definition is the new Definition to store.
   * @param overwrite true, if existing defitions will be overwritten by
   * new ones with the same primary (or secondary) key (-set), or false,
   * if a new definition will be rejected on key matches.
   *
   * @return true, if the backend database was changed, or
   *         false, if the definition was not accepted into the backend.
   *
   * @see org.griphyn.vdl.classes.Definition
   * @see org.griphyn.vdl.classes.Transformation
   * @see org.griphyn.vdl.classes.Derivation
   * @see #loadDefinition( String, String, String, int )
   * @see #deleteDefinition( String, String, String, int ) 
   */
  public boolean 
    saveDefinition( Definition definition, 
		    boolean overwrite )
    throws SQLException
  {
    int position = this.m_memory.positionOfDefinition(definition);
    if ( position != -1 ) {
      // definition already exists
      if ( overwrite ) {
        Logging.instance().log( "app", 1, "Modifying " + definition.shortID() );
        this.m_memory.setDefinition( position, definition );
        return true;
      } else {
        Logging.instance().log( "app", 1, "Rejecting " + definition.shortID() );
        return false;
      }
    } else {
      // definition does not exist
      Logging.instance().log( "app", 1, "Adding " + definition.shortID() );
      this.m_memory.addDefinition(definition);
      return true;
    }
  }



  //
  // higher level methods, allowing for wildcarding as stated.
  //

  /**
   * Check with the backend database, if the given definition exists.
   *
   * @param definition  is a Definition object to search for
   * @return true, if the Definition exists, false if not found
   */
  public boolean 
    containsDefinition( Definition definition ) 
    throws SQLException
  {
    return ( this.m_memory.positionOfDefinition(definition) != -1 );
  }

  /**
   * Delete a specific Definition objects from the database. No wildcard
   * matching will be done. "Fake" definitions are permissable, meaning
   * it just has the secondary key triple.<p>
   * This method is not implemented!
   *
   * @param definition is the definition specification to delete
   * @return  true is something was deleted, false if non existent.
   *
   * @see org.griphyn.vdl.classes.Definition#TRANSFORMATION
   * @see org.griphyn.vdl.classes.Definition#DERIVATION 
   */
  public boolean 
    deleteDefinition( Definition definition )
    throws SQLException
  {
    return this.m_memory.removeDefinition(definition);
  }

  /**
   * Delete one or more definitions from the backend database. Depending
   * on the matchAll flag the key triple parameters may be wildcards.
   * Wildcards are expressed as <code>null</code> value.<p>
   * This method is not implemented!
   *
   * @param namespace   namespace
   * @param name        name
   * @param version     version
   * @param type        definition type (TR or DV)
   * @return            a list of definitions that were deleted.
   *
   * @see org.griphyn.vdl.classes.Definition#TRANSFORMATION
   * @see org.griphyn.vdl.classes.Definition#DERIVATION 
   */
  public java.util.List 
    deleteDefinition( String namespace,
		      String name,
		      String version,
		      int type )
    throws SQLException
  {
    java.util.List result = new ArrayList();

    // walk the database
    for ( ListIterator i=this.m_memory.listIterateDefinition(); i.hasNext(); ) {
      Definition d = (Definition) i.next();
      if ( type == -1 || d.getType() == type ) {
        // yes, type matches, let's continue
        String ns = d.getNamespace();
        String id = d.getName();
        String vs = d.getVersion();

	if ( ( namespace == null || // match all for null argument
	       ns != null && ns.equals(namespace) ) &&
	     ( name == null || // match all for null argument
	       id != null && id.equals(name) ) &&
	     ( version == null || // match all for null argument
	       vs != null && vs.equals(version) ) ) {
	  // there was a match including nulls and jokers etc. 
	  result.add(d);
	  i.remove();
	}
      }
    }

    return result;
  }

  /**
   * Search the database for definitions by ns::name:version triple
   * and by type (either Transformation or Derivation). This version
   * of the search allows for jokers expressed as null value.<p>
   * This method is not implemented!
   *
   * @param namespace   namespace, null to match any namespace
   * @param name        name, null to match any name
   * @param version     version, null to match any version
   * @param type        type of definition, see below, or -1 as wildcard
   * @return            a list of Definition items, which may be empty
   *
   * @see org.griphyn.vdl.classes.Definition#TRANSFORMATION
   * @see org.griphyn.vdl.classes.Definition#DERIVATION
   * @see #loadDefinition( String, String, String, int )
   */
  public java.util.List 
    searchDefinition( String namespace,
		      String name,
		      String version,
		      int type )
    throws SQLException
  {
    java.util.List result = new ArrayList();

    // walk the database
    for ( ListIterator i=this.m_memory.listIterateDefinition(); i.hasNext(); ) {
      Definition d = (Definition) i.next();
      if ( type == -1 || d.getType() == type ) {
        // yes, type matches, let's continue
        String ns = d.getNamespace();
        String id = d.getName();
        String vs = d.getVersion();

        if ( ( namespace == null || // match all for null argument
               ns != null && ns.equals(namespace) ) &&
             ( name == null || // match all for null argument
               id != null && id.equals(name) ) &&
             ( version == null || // match all for null argument
               vs != null && vs.equals(version) ) ) {
          result.add(d);
        }
      }
    }

    return result;
  }


  /**
   * Searches the database for all derivations that contain a certain LFN.
   * The linkage is an additional constraint. This method does not allow
   * jokers.
   *
   * @param lfn    the LFN name
   * @param link   the linkage type of the LFN
   * @return       a list of Definition items that match the criterion.
   *
   * @see org.griphyn.vdl.classes.LFN#NONE
   * @see org.griphyn.vdl.classes.LFN#INPUT
   * @see org.griphyn.vdl.classes.LFN#OUTPUT
   * @see org.griphyn.vdl.classes.LFN#INOUT
   */
  public java.util.List 
    searchFilename( String lfn, 
		    int link )
    throws SQLException
  {
    java.util.List result = new ArrayList();

    // check all Derivations (this may be time consuming!)
    for ( Iterator i=this.m_memory.iterateDefinition(); i.hasNext(); ) {
      Definition d = (Definition) i.next();
      if ( d instanceof Derivation ) {
        Derivation dv = (Derivation) d;
        for ( Iterator j=dv.iteratePass(); j.hasNext(); ) {
          boolean found = false;
          Value actual = ((Pass) j.next()).getValue();
          switch ( actual.getContainerType() ) {
          case Value.SCALAR:
            // this is a regular SCALAR
            if ( scalarContainsLfn( (Scalar) actual, lfn, link ) ) {
              // Logging.instance().log("search", 2, "found " + dv.shortID());
              result.add(dv);
              found = true;
            }
            break;
          case Value.LIST:
            // a LIST is a list of SCALARs
            org.griphyn.vdl.classes.List list = 
              (org.griphyn.vdl.classes.List) actual;
            for ( Iterator f = list.iterateScalar(); f.hasNext() ; ) {
              if ( scalarContainsLfn( (Scalar) f.next(), lfn, link) ) {
                // Logging.instance().log("search", 2, "found " + dv.shortID());
                result.add(dv);
                found = true;
                
                // skip all other scalars
                break;
              }
            }
            break;
          default:
            // this should not happen
            Logging.instance().log( "default", 0,
                                    "WARNING: An actual argument \"" +
                                    actual.toString() +
                                    "\" is neither SCALAR nor LIST" );
            break;
          }

          // if found in one Pass, skip all the others
          if ( found )
            break;
        }
      }
    }

    return result;
  }

  /**
   * This helper function checks, if a given Scalar instance
   * contains the specified logical filename as LFN instance anywhere
   * in its sub-structures.
   *
   * @param scalar is a Scalar instance to check
   * @param lfn is a logical filename string to check for
   * @param link is the linkage type of the lfn. 
   * if -1, do not check the linkage type.
   * @return true, if the file was found
   */
  protected boolean scalarContainsLfn( Scalar scalar, String lfn, int link )
  {
    for ( Iterator e = scalar.iterateLeaf(); e.hasNext(); ) {
      org.griphyn.vdl.classes.Leaf leaf =
        (org.griphyn.vdl.classes.Leaf) e.next();
      if ( leaf instanceof LFN ) {
        LFN local = (LFN) leaf;
        if ( (link == -1 || local.getLink() == link) &&
             lfn.compareTo( local.getFilename() ) == 0 )
          return true;
      }
    }
    return false;
  }
}
